package org.github.core.logger;

import cn.hutool.core.date.TimeInterval;
import javax.annotation.Nullable;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.github.core.base.ILogger;
import org.github.core.base.Json;
import org.slf4j.Logger;
import static java.lang.String.valueOf;
import static java.lang.System.currentTimeMillis;
import static java.lang.ThreadLocal.withInitial;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.joining;

@Aspect
@Slf4j
public class MethodInvoke {
    private final ThreadLocal<TimeIntervalOps> timer = withInitial(TimeIntervalOps::new);

    @AfterReturning(value = "pointcut()", returning = "value")
    public void afterReturning(JoinPoint point, @Nullable Object value) {
        val name = point.getSignature().toShortString();
        val log = getLogger(point);
        log.debug("返回结果: {} ====> {}", name, buildInfo(value));
    }

    @AfterThrowing(value = "pointcut()", throwing = "e")
    public void afterThrowing(JoinPoint point, Exception e) {
        val name = point.getSignature().toShortString();
        val log = getLogger(point);
        log.debug("抛出异常: {} ====> {}", name, e.getMessage());
    }

    @SneakyThrows
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint point) {
        val log = getLogger(point);
        val timer = this.timer.get();
        val group = valueOf(currentTimeMillis());
        try {
            timer.start(group);
            return point.proceed();
        } finally {
            val cost = timer.remove(group);
            val name = point.getSignature().toShortString();
            if (cost >= 3000) {
                log.warn("耗时统计: {} ====> {}ms", name, cost);
            } else {
                log.debug("耗时统计: {} ====> {}ms", name, cost);
            }
        }
    }

    @Before("pointcut()")
    public void before(JoinPoint point) {
        val name = point.getSignature().toShortString();
        val args = point.getArgs();
        val log = getLogger(point);
        log.debug("执行方法: {} ====> {}", name, buildInfo(args));
    }

    @Pointcut("execution(* org.github..*Controller.*(..)) || execution(* org.github..*Service.*(..)) || execution(* org.github..*Mapper.*(..))")
    public void pointcut() {
    }

    private Logger getLogger(JoinPoint point) {
        val target = point.getTarget();
        if (target instanceof ILogger) {
            return ((ILogger) target).getLog();
        }
        return log;
    }

    static String buildInfo(Object arg) {
        return arg instanceof Json ? ((Json) arg).get() : valueOf(arg);
    }

    static String buildInfo(Object[] args) {
        return stream(args).map(MethodInvoke::buildInfo).collect(joining(", ", "[", "]"));
    }

    public static class TimeIntervalOps extends TimeInterval {
        long remove(String group) {
            val cost = interval(group);
            groupMap.remove(group);
            return cost;
        }
    }
}
